package com.spukmk3me.impl.libgdx;

/* $if SPUKMK3ME_DEBUG$ */
import com.spukmk3me.debug.Logger;
/* $endif$ */
import com.spukmk3me.math.Matrix4f;
import com.spukmk3me.math.Rectangle;
import com.spukmk3me.math.Vector3f;
import com.spukmk3me.util.Util;
import com.spukmk3me.video.Polygon;
import com.spukmk3me.video.Text;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes.Usage;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;

class RenderBuffer
{
    RenderBuffer() {}
    
    /* $if SPUKMK3ME_DEBUG$ */
    void resetFlushCounter()
    {
        //Logger.log( "Number of flushes: " + m_flushCounter + '\n' );
        m_flushCounter = 0;
    }
    /* $endif$ */
    
    void init()
    {
        m_mesh = new Mesh( false, VERTEXSIZE, INDEXSIZE,
            new VertexAttribute( Usage.Position, 2, ShaderProgram.POSITION_ATTRIBUTE ),
            new VertexAttribute( Usage.ColorPacked, 4, ShaderProgram.COLOR_ATTRIBUTE ),
            new VertexAttribute( Usage.TextureCoordinates, 2, ShaderProgram.TEXCOORD_ATTRIBUTE ) );
        
        m_vertexData = new float[ VERTEXSIZE * 5 ];
        m_indexData = new short[ INDEXSIZE ];
        m_indexOfData = m_indexOfIndices = 0;
        
        //
        m_tempVertex   = new Vector3f();
        m_mvMatrix      = new Matrix4f();
    }
    
    void dispose()
    {
        m_mesh.dispose();
    }
    
    void flush()
    {
        if ( m_indexOfData == 0 )
            return;
        
        m_mesh.setVertices( m_vertexData, 0, m_indexOfData );
        m_mesh.getVerticesBuffer().position( 0 );
        m_mesh.getVerticesBuffer().limit( m_indexOfData );

        m_mesh.setIndices( m_indexData, 0, m_indexOfIndices );
        m_mesh.getIndicesBuffer().position( 0 );
        m_mesh.getIndicesBuffer().limit( m_indexOfIndices ); 
        
        m_mesh.render( GL10.GL_TRIANGLES, 0, m_indexOfIndices );
        m_indexOfData = m_indexOfIndices = 0;
        
        /* $if SPUKMK3ME_DEBUG$ */
        ++m_flushCounter;
        /* $endif$ */
    }
    
    void _drawImg( LibgdxImage texture, float w, float h,
        float u1, float v1, float u2, float v2, float color )
    {
        switchTexture( texture.gdxTexture );
        checkFlushBuffer( 4, 6 );
        
        short firstVertexIndex = (short)(m_indexOfData / 5);
        
        m_tempVertex.set( 0f, 0f, 0f );
        m_tempVertex.mul( m_mvMatrix );
        
        m_vertexData[ m_indexOfData++ ] = m_tempVertex.x;
        m_vertexData[ m_indexOfData++ ] = m_tempVertex.y;
        m_vertexData[ m_indexOfData++ ] = color;
        m_vertexData[ m_indexOfData++ ] = u1;
        m_vertexData[ m_indexOfData++ ] = v1;
        
        m_tempVertex.set( w, 0f, 0f );
        m_tempVertex.mul( m_mvMatrix );
        
        m_vertexData[ m_indexOfData++ ] = m_tempVertex.x;
        m_vertexData[ m_indexOfData++ ] = m_tempVertex.y;
        m_vertexData[ m_indexOfData++ ] = color;
        m_vertexData[ m_indexOfData++ ] = u2;
        m_vertexData[ m_indexOfData++ ] = v1;
        
        m_tempVertex.set( w, h, 0f );
        m_tempVertex.mul( m_mvMatrix );
        
        m_vertexData[ m_indexOfData++ ] = m_tempVertex.x;
        m_vertexData[ m_indexOfData++ ] = m_tempVertex.y;
        m_vertexData[ m_indexOfData++ ] = color;
        m_vertexData[ m_indexOfData++ ] = u2;
        m_vertexData[ m_indexOfData++ ] = v2;
        
        m_tempVertex.set( 0f, h, 0f );
        m_tempVertex.mul( m_mvMatrix );
        
        m_vertexData[ m_indexOfData++ ] = m_tempVertex.x;
        m_vertexData[ m_indexOfData++ ] = m_tempVertex.y;
        m_vertexData[ m_indexOfData++ ] = color;
        m_vertexData[ m_indexOfData++ ] = u1;
        m_vertexData[ m_indexOfData++ ] = v2;
        
        m_indexData[ m_indexOfIndices++ ] = firstVertexIndex;
        m_indexData[ m_indexOfIndices++ ] = (short)(firstVertexIndex + 1);
        m_indexData[ m_indexOfIndices++ ] = (short)(firstVertexIndex + 2);
        m_indexData[ m_indexOfIndices++ ] = firstVertexIndex;
        m_indexData[ m_indexOfIndices++ ] = (short)(firstVertexIndex + 2);
        m_indexData[ m_indexOfIndices++ ] = (short)(firstVertexIndex + 3);
    }
        
    void _drawBlankPolygon( Texture texture, Polygon poly, float color )
    {
        float[] coords = poly.getCoords();
        int nVertex = coords.length / 2;
        
        switchTexture( texture, GL10.GL_NEAREST, GL10.GL_NEAREST );
        checkFlushBuffer( nVertex, (nVertex - 2) * 3 );
        
        short startVertexIndex = (short)(m_indexOfData / 5);
        int cIndex = 0;
        
        for ( int i = 0; i != nVertex; ++i )
        {
            m_tempVertex.set( coords[ cIndex ], coords[ cIndex + 1 ], 0f );
            m_tempVertex.mul( m_mvMatrix );
            m_vertexData[ m_indexOfData++ ] = m_tempVertex.x;
            m_vertexData[ m_indexOfData++ ] = m_tempVertex.y;
            m_vertexData[ m_indexOfData++ ] = color;
            m_vertexData[ m_indexOfData++ ] = 0f;
            m_vertexData[ m_indexOfData++ ] = 0f;
            cIndex += 2;
        }
        
        for ( int i = 0; i != nVertex - 2; ++i )
        {
            m_indexData[ m_indexOfIndices++ ] = startVertexIndex;
            m_indexData[ m_indexOfIndices++ ] = (short)(startVertexIndex + i + 1);
            m_indexData[ m_indexOfIndices++ ] = (short)(startVertexIndex + i + 2);
        }
    }
    
    void _drawText( Text text, float color )
    {
        char[] s = text.getFilteredString();
        
        if ( s == null )
            return;
        
        switchTexture( ((LibgdxImage)text.getFont().getImage()).gdxTexture, GL10.GL_LINEAR, GL10.GL_LINEAR );
        checkFlushBuffer( s.length * 4, s.length * 6 );

        Rectangle dimension;
        int     x = 0, y = 0;
        short   firstVertexIndex = (short)(m_indexOfData / 5);
        boolean additionalSpace = false; // Small space after each non-space character.
        char    c = 0;
        
        for ( int i = 0; i != s.length; ++i )
        {
            c = s[ i ];
            
            if ( c == '\n' )
            {
                x = 0;
                y -= text.getFont().getLineHeight();
                additionalSpace = false;
            }
            else
            {
                dimension = text.getFont().getCharacterDimension( c );
                
                // Spaces are invisible.
                if ( c != ' ' )
                {
                    m_tempVertex.set( x, y, 0f );
                    m_tempVertex.mul( m_mvMatrix );
                    
                    m_vertexData[ m_indexOfData++ ] = m_tempVertex.x;
                    m_vertexData[ m_indexOfData++ ] = m_tempVertex.y;
                    m_vertexData[ m_indexOfData++ ] = color;
                    m_vertexData[ m_indexOfData++ ] = dimension.x;
                    m_vertexData[ m_indexOfData++ ] = dimension.y + dimension.h;
                    
                    m_tempVertex.set( x + dimension.w, y, 0f );
                    m_tempVertex.mul( m_mvMatrix );
                    
                    m_vertexData[ m_indexOfData++ ] = m_tempVertex.x;
                    m_vertexData[ m_indexOfData++ ] = m_tempVertex.y;
                    m_vertexData[ m_indexOfData++ ] = color;
                    m_vertexData[ m_indexOfData++ ] = dimension.x + dimension.w;
                    m_vertexData[ m_indexOfData++ ] = dimension.y + dimension.h;
                    
                    m_tempVertex.set( x + dimension.w, y + dimension.h, 0f );
                    m_tempVertex.mul( m_mvMatrix );
                    
                    m_vertexData[ m_indexOfData++ ] = m_tempVertex.x;
                    m_vertexData[ m_indexOfData++ ] = m_tempVertex.y;
                    m_vertexData[ m_indexOfData++ ] = color;
                    m_vertexData[ m_indexOfData++ ] = dimension.x + dimension.w;
                    m_vertexData[ m_indexOfData++ ] = dimension.y;
                    
                    m_tempVertex.set( x, y + dimension.h, 0f );
                    m_tempVertex.mul( m_mvMatrix );
                    
                    m_vertexData[ m_indexOfData++ ] = m_tempVertex.x;
                    m_vertexData[ m_indexOfData++ ] = m_tempVertex.y;
                    m_vertexData[ m_indexOfData++ ] = color;
                    m_vertexData[ m_indexOfData++ ] = dimension.x;
                    m_vertexData[ m_indexOfData++ ] = dimension.y;
                    
                    m_indexData[ m_indexOfIndices++ ] = firstVertexIndex;
                    m_indexData[ m_indexOfIndices++ ] = (short)(firstVertexIndex + 1);
                    m_indexData[ m_indexOfIndices++ ] = (short)(firstVertexIndex + 2);
                    m_indexData[ m_indexOfIndices++ ] = firstVertexIndex;
                    m_indexData[ m_indexOfIndices++ ] = (short)(firstVertexIndex + 2);
                    m_indexData[ m_indexOfIndices++ ] = (short)(firstVertexIndex + 3);
                    firstVertexIndex += 4;

                    x += text.getFont().getSpaceBetweenChar() + text.getFont().getCharacterDimension( c ).w;
                    additionalSpace = true;
                }
                else
                {
                    if ( additionalSpace )
                        x -= text.getFont().getSpaceBetweenChar();

                    x += text.getFont().getCharacterDimension( ' ' ).w;
                    additionalSpace = false;
                }
            }
        }
    }
    
    void _setModelViewMatrix( Matrix4f m )
    {
        m_mvMatrix.copy( m );
    }
    
    void glContextCreated()
    {
        switchTexture( null );
    }
    
    private void checkFlushBuffer( int nVertexes, int nIndices )
    {
        if ( (m_indexOfData + nVertexes * 5 >= VERTEXSIZE) || (m_indexOfIndices + nIndices >= INDEXSIZE ) )
            flush();
    }
    
    private void switchTexture( Texture texture )
    {
        if ( m_lastTexture != texture )
        {
            flush();
            
            if ( texture != null )
            {
                texture.bind();
                //Gdx.gl.glTexParameterf( GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, texture.minFilter );
                //Gdx.gl.glTexParameterf( GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, texture.magFilter );
            }
        }
        
        m_lastTexture = texture;
    }
    
    private void switchTexture( Texture texture, int magFilter, int minFilter )
    {
        if ( m_lastTexture != texture )
        {
            flush();
            
            if ( texture != null )
            {
                texture.bind();
                Gdx.gl.glTexParameterf( GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, minFilter );
                Gdx.gl.glTexParameterf( GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, magFilter );
            }
            
            m_lastTexture = texture;
        }
    }
    
    private static final int VERTEXSIZE = 256;
    private static final int INDEXSIZE  = 256;
    
    /*private ByteBuffer          m_vertexBufferB, m_indicesBufferB;*/
    private Texture             m_lastTexture;
    private Mesh                m_mesh;
    private Rectangle           m_clipRect;
    private float[]             m_vertexData;
    private short[]             m_indexData;
    private int                 m_indexOfData, m_indexOfIndices;
    
    // Branch
    private Matrix4f            m_mvMatrix;
    private Vector3f            m_tempVertex;
    
    /* $if SPUKMK3ME_DEBUG$ */
    private int                 m_flushCounter;
    /* $endif$ */
}
